/*
 * Copyright (C) 2014 Alpha, Inc.
 * This is NOT a freeware, use is subject to license terms.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cc.gehua.octopus.rpc.common.resource;

import cc.gehua.octopus.rpc.utils.URIUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.ReadableByteChannel;
import java.security.Permission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author guna
 */
public class URLResource extends Resource {

    private static final Logger LOG = LoggerFactory.getLogger(URLResource.class);
    protected final URL url;
    protected final String urlString;

    protected URLConnection connection;
    protected InputStream in = null;
    transient boolean useCaches = Resource.defaultUseCaches;

    /* ------------------------------------------------------------ */
    protected URLResource(URL url, URLConnection connection) {
        this.url = url;
        urlString = this.url.toExternalForm();
        this.connection = connection;
    }

    /* ------------------------------------------------------------ */
    protected URLResource(URL url, URLConnection connection, boolean useCaches) {
        this(url, connection);
        this.useCaches = useCaches;
    }

    /* ------------------------------------------------------------ */
    protected synchronized boolean checkConnection() {
        if (connection == null) {
            try {
                connection = url.openConnection();
                connection.setUseCaches(useCaches);
            } catch (IOException e) {

            }
        }
        return connection != null;
    }

    /* ------------------------------------------------------------ */
    /**
     * Release any resources held by the resource.
     */
    @Override
    public synchronized void close() {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
            }
            in = null;
        }

        if (connection != null) {
            connection = null;
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns true if the represented resource exists.
     * @return 
     */
    @Override
    public boolean exists() {
        try {
            synchronized (this) {
                if (checkConnection() && in == null) {
                    in = connection.getInputStream();
                }
            }
        } catch (IOException e) {

        }
        return in != null;
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns true if the represented resource is a container/directory. If the
     * resource is not a file, resources ending with "/" are considered
     * directories.
     * @return 
     */
    @Override
    public boolean isDirectory() {
        return exists() && urlString.endsWith("/");
    }


    /* ------------------------------------------------------------ */
    /**
     * Returns the last modified time
     * @return 
     */
    @Override
    public long lastModified() {
        if (checkConnection()) {
            return connection.getLastModified();
        }
        return -1;
    }


    /* ------------------------------------------------------------ */
    /**
     * Return the length of the resource
     * @return 
     */
    @Override
    public long length() {
        if (checkConnection()) {
            return connection.getContentLength();
        }
        return -1;
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns an URL representing the given resource
     * @return 
     */
    @Override
    public URL getURL() {
        return url;
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns an File representing the given resource or NULL if this is not
     * possible.
     * @return 
     * @throws java.io.IOException 
     */
    @Override
    public File getFile()
            throws IOException {
        // Try the permission hack
        if (checkConnection()) {
            Permission perm = connection.getPermission();
            if (perm instanceof java.io.FilePermission) {
                return new File(perm.getName());
            }
        }

        // Try the URL file arg
        try {
            return new File(url.getFile());
        } catch (Exception e) {
        }

        // Don't know the file
        return null;
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns the name of the resource
     * @return 
     */
    @Override
    public String getName() {
        return url.toExternalForm();
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns an input stream to the resource. The underlying url connection
     * will be nulled out to prevent re-use.
     * @return 
     * @throws java.io.IOException 
     */
    @Override
    public synchronized InputStream getInputStream()
            throws IOException {
        return getInputStream(true); //backwards compatibility
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns an input stream to the resource, optionally nulling out the
     * underlying url connection. If the connection is not nulled out, a
     * subsequent call to getInputStream() may return an existing and already
     * in-use input stream - this depends on the url protocol. Eg
     * JarURLConnection does not reuse inputstreams.
     *
     * @param resetConnection if true the connection field is set to null
     * @return 
     * @throws java.io.IOException 
     */
    protected synchronized InputStream getInputStream(boolean resetConnection)
            throws IOException {
        if (!checkConnection()) {
            throw new IOException("Invalid resource");
        }

        try {
            if (in != null) {
                InputStream inst = this.in;
                this.in = null;
                return inst;
            }
            return connection.getInputStream();
        } finally {
            if (resetConnection) {
                connection = null;
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Connection nulled");
                }
            }
        }
    }

    /* ------------------------------------------------------------ */
    @Override
    public ReadableByteChannel getReadableByteChannel() throws IOException {
        return null;
    }

    /* ------------------------------------------------------------ */
    /**
     * Deletes the given reso
     * @return urce
     */
    @Override
    public boolean delete()
            throws SecurityException {
        throw new SecurityException("Delete not supported");
    }

    /* ------------------------------------------------------------ */
    /**
     * Rename the given resource
     * @return 
     */
    @Override
    public boolean renameTo(Resource dest)
            throws SecurityException {
        throw new SecurityException("RenameTo not supported");
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns a list of resource names contained in the given resource
     * @return 
     */
    @Override
    public String[] list() {
        return null;
    }

    /* ------------------------------------------------------------ */
    /**
     * Returns the resource contained inside the current resource with the given
     * name
     * @return 
     * @throws java.io.IOException 
     * @throws java.net.MalformedURLException 
     */
    @Override
    public Resource addPath(String path)
            throws IOException, MalformedURLException {
        if (path == null) {
            return null;
        }

        path = URIUtil.canonicalPath(path);

        return newResource(URIUtil.addPaths(url.toExternalForm(), path));
    }

    /* ------------------------------------------------------------ */
    @Override
    public String toString() {
        return urlString;
    }

    /* ------------------------------------------------------------ */
    @Override
    public int hashCode() {
        return urlString.hashCode();
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean equals(Object o) {
        return o instanceof URLResource && urlString.equals(((URLResource) o).urlString);
    }

    /* ------------------------------------------------------------ */
    public boolean getUseCaches() {
        return useCaches;
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean isContainedIn(Resource containingResource) throws MalformedURLException {
        return false;
    }
}
